/*
 * ConnectThread.java
 *
 * Created on 23.11.2007, 13:03:04
 *
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cz.possoft.client.threads;

import java.io.EOFException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;

import cz.possoft.client.gui.applet.PossoftNetChess;
import cz.possoft.client.gui.dialog.SimpleDialog;
import cz.possoft.client.gui.dialog.VyzivaciDialog;
import cz.possoft.client.gui.panel.ChatPanel;
import cz.possoft.client.gui.panel.GamePanel;
import cz.possoft.client.gui.panel.nastavenispodnipanel.panel.NacteniShowPanel;
import cz.possoft.client.gui.panel.nastavenispodnipanel.panel.NastaveniShowPanel;
import cz.possoft.client.gui.panel.nastavenispodnipanel.panel.RemizaShowPanel;
import cz.possoft.client.loader.ClassLoaderObjectInputStream;
import cz.possoft.client.saveload.SaveLoadException;
import cz.possoft.client.saveload.SaveLoadManager;
import cz.possoft.client.utils.GameInvocationUtils;
import cz.possoft.client.utils.PanelHolderUtils;
import cz.possoft.client.utils.SaveLoadUtils;
import cz.possoft.generic.utils.GenericUtils;
import cz.possoft.messages.PropertiesManager;
import cz.possoft.messages.Text;
import cz.possoft.serializable.generic.Game;

/**
 * Tato trida slouzi k pripojovani na vzdaleny server a k udrzovani komunikace se serverem.
 * Pripojovani bezi v samostatnem vlakne stejne jako veskera komunikace se serverem.
 * 
 * @author mara
 *
 */
public class ConnectThread extends Thread {

    // klientsky socket a jeho reader a writer
    private Socket clientSocket = null;
    private ObjectInputStream oinputReader;
    private ObjectOutputStream ooutputWriter;
    
    // jmeno klienta a oponenta
    private String clientName;
    private String opponentName;
    
    // klic hry, napr. Chess
    private String gameKey;
    
    // priznak zda bylo vlakno inicializovano a bezi
    private boolean initFlag = false;
    
    // priznak, zda jsme pripojeni
    private boolean connectFlag = false;
    
    // v tomto stringu chodi odpoved od serveru
    private String response;
         
    // tato promenna pocita, kolikrat doslo NULL od oponenta. NULL musi dojit aspon dvakrat, abysme
    // brali v potaz, ze je oponent skutecne NULL. Reseni neni uplne nejlepsi, ale melo by
    // fungovat pro standardni pripady....
    private int nullCounter = 0;
    private static final int NULL_COUNTER_COUNT = 2;

    public boolean getInitFlag() {
        return initFlag;
    }

    // inicializace vlakna
    public void init(String clientName) {                
        this.clientName = clientName;
        this.opponentName = "";        
        clientSocket = null;
        initFlag = true;
        this.start();
    }

    // deinicializace vlakna. Nastava napr. pri zavreni socketu
    public void dispose() {
        try {
            initFlag = false;
            connectFlag = false;
            clientName = "";
            opponentName = "";
            NastaveniShowPanel.getListOfUsers().clear();
            NastaveniShowPanel.getVyzivaciButton().setEnabled(false);
            oinputReader.close();
            ooutputWriter.close();
            clientSocket.close();
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    public String getClientName() {
        return clientName;
    }

    public void setClientName(String cName) {
        clientName = cName;
    }

    public String getOpponentName() {
        return opponentName;
    }

    public void setOpponentName(String opponentName) {
        this.opponentName = opponentName;
    }
    
    public String getGameKey() {
        return gameKey;
    }

    public void setGameKey(String gk) {
        this.gameKey = gk;
    }    

    public Socket getClientSocket() {
        return clientSocket;
    }
    
    public boolean getConnectFlag() {
    	return connectFlag;
    }

    
    public void run() {
        
        // pripad, kdy nejsme pripojeni k serveru
        if (clientSocket == null) {
        	//System.out.println("connectThread: bod 1");
        	//HttpRequestThread httpRequestThread = new HttpRequestThread(this);
            boolean repeatFlag = true;  
            int bod3Counter = 0; // counter je zde proto, ze pokud se to podruhe dostane do bodu 3, tak se musi ukoncit vlakno.
            // a nevolat synchronizovane cekani.
            while (repeatFlag) {
                try {
                    sleep(3000);
                    if (clientSocket != null) {
                    	repeatFlag = false;
                    }
                    else {
                    	//System.out.println("connectThread: bod 2");
                        clientSocket = new Socket(InetAddress.getByName(SaveLoadUtils.returnHostName()), GenericUtils.PORT);
                        // pokud se to dostane az sem, tak jsme se pripojili
                        connectFlag = true;
                    }
                } catch (SocketException se) {
                	//try {
                		// cekame dokud se httpThread nepokusi spustit PossoftNetServer pres cgi skript
                		//System.out.println("connectThread: bod 3");
                		bod3Counter++;
                		/*if (bod3Counter <= 1) {
                	    synchronized (this) {
                		     wait();
                	     }
                		}
                		else {*/
                			repeatFlag = false;
                		/*}
                	} catch (InterruptedException ie) {
                		ie.printStackTrace();
                	}*/
                	//System.out.println("connectThread: bod 4");
                    // Poslani http requestu pro pusteni PossoftNetServer programu na serveru. 
                	// Pro neposilani tohoto dotazu staci zakomentovat tento radek a odkomentovat
                	// nasledujici radek.
                    //repeatFlag = sendingHttpRequestToFi(GenericUtils.CGI_SCRIPT_TO_HTTP_REQUEST_ADDRESS, repeatFlag);
                    //repeatFlag = false;
                } catch (Exception eeee) {
                    eeee.printStackTrace();                    
                    repeatFlag = false;
                }
            }                
                    try {                        
                    	// TODO: Workaround to avoid NullPointerException in client's log.
                    	if (clientSocket == null) {
                    		throw new SocketException("Can't connect to server " + SaveLoadUtils.returnHostName() + " at port " + GenericUtils.PORT);
                    	}
                        ooutputWriter = new ObjectOutputStream(clientSocket.getOutputStream());
                        oinputReader = new ClassLoaderObjectInputStream(clientSocket.getInputStream());
                        NastaveniShowPanel.getConnectExceptionText().setText("");
                        NastaveniShowPanel.getConnectLabel().connected();
                        sendToServer("CONNECT: " + clientName);
                        repeatFlag = false;
                    } catch (Exception e) {
                        e.printStackTrace();
                        SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_NADPIS), PropertiesManager.getInstance().getMessageForKey(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_IN_CONNECTING)));
                        initFlag = false;
                        repeatFlag = false;
                    }
                }                   

        // pripad kdy jsme pripojeni k serveru
        if (clientSocket != null) {
            while (true) {
                try {
                    Object o = oinputReader.readObject();

                    if (o instanceof String) {
                        response = (String) o;

                        if (response != null) {
                            // pokus o pripojeni s jmenem, ktere uz existuje
                            if (response.indexOf("NAME_PRESENT: ") == 0) {
                                clientName = ""; // vynulovani klientova jmena
                                NastaveniShowPanel.getConnectExceptionText().setText(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_NAME_PRESENT));
                                NastaveniShowPanel.getConnectLabel().notConnected();
                            }

                            // dosel seznam jmen pripojenych klientu. Musime tedy upravit list.
                            if (response.indexOf("CONNECTED: ") == 0) {
                            	NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);
                            }

                            // dosla vyzva
                            if (response.indexOf("VYZVAL: ") == 0) {
                                // pokud me nekdo vyzval, tak se automaticky nastavi opponentName a zobrazi se vyzivaci dialog
                                opponentName = GenericUtils.getVyzivatelName(response);
                                gameKey = GenericUtils.getGameKey(response);
                                VyzivaciDialog.getInstance(opponentName, gameKey);
                            }

                            // ze serveru doslo potvrzeni me vyzvy
                            if (response.indexOf("VYZVA_POTVRZENA: ") == 0) {
                                String opName = response.substring(17);
                                potvrzeni_vyzvy(opName);
                            }

                            // ze serveru doslo, ze oponenta uz vyzval nekdo jinej nebo nekoho jineho vyzval oponent
                            if (response.indexOf("OTHER: ") == 0) {
                                opponentName = "";
                                NastaveniShowPanel.getConnectExceptionText().setText("");
                                NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);
                                SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.OTHER_OPPONENT_DIALOG_NADPIS), PropertiesManager.getInstance().getMessageForKey(Text.OTHER_OPPONENT_DIALOG_ZPRAVA));
                            }


                            // souper akceptoval nasi vyzvu
                            if (response.indexOf("ACCEPT: ") == 0) {
                            	NastaveniShowPanel.getConnectExceptionText().setText("");
                                // TADY ZACINA NOVA HRA. Ja jako vyzivatel hraju za cerne
                                setNewGame(opponentName, clientName, gameKey);
                            }

                            // souper neakceptoval nasi vyzvu
                            if (response.indexOf("NOT_ACCEPT: ") == 0) {
                                opponentName = "";
                                NastaveniShowPanel.getConnectExceptionText().setText("");
                                SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.ZAMITACI_DIALOG_NADPIS), PropertiesManager.getInstance().getMessageForKey(Text.ZAMITACI_DIALOG_ZPRAVA));
                                NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);
                            }

                            // spadlo spojeni se souperem nebo s hracem, ktereho vyzivame
                            if (response.indexOf("NULL: ") == 0) {
                                if (opponentName.equals("") == false) {
                                    if (nullCounter == NULL_COUNTER_COUNT - 1) {
                                        String pomOpponentName = opponentName;
                                        //SimpleDialog.getInstance(Text.ERROR_OPPONENT_FALL_NADPIS, Text.getErrorOpponentFallMessage(pomOpponentName));
                                        NastaveniShowPanel.getConnectExceptionText().setText("");
                                        opponentName = "";
                                        NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);

                                        //ChessPanel.setGame(new Game().setNoGame());
                                        PanelHolderUtils.getCurrentGamePanel().setActiveToGame(false);
                                        SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_NADPIS), Text.getErrorOpponentFallMessage(pomOpponentName));
                                        nullCounter = 0;
                                    } else {
                                        nullCounter++;
                                    }
                                } else {
                                    nullCounter = 0;
                                }
                            }

                            // dosla zprava ode me nebo od protivnika
                            if (response.indexOf("GET_MESSAGE: ") == 0) {
                                ChatPanel.getChatTextPane().append(response.substring(13));
                            }

                            // dosel protivnikuv tah
                            if (response.indexOf("PLAY: ") == 0) {
                            	String[] tahy = response.split("_");                            	
                                int oldX = Integer.parseInt(tahy[1]);
                                int oldY = Integer.parseInt(tahy[2]);
                                int newX = Integer.parseInt(tahy[3]);
                                int newY = Integer.parseInt(tahy[4]);
                                PanelHolderUtils.getCurrentGamePanel().doPlay(oldX, oldY, newX, newY);                                
                            }

                            // dosle od protivnika navrh na remizu
                            if (response.indexOf("REMIZA_NAVRH: ") == 0) {
                                VyzivaciDialog.getInstanceRemiza();
                            }

                            // dosle od protivnika navrh na load stare hry
                            if (response.indexOf("LOAD_GAME_VYZVA: ") == 0) {
                                VyzivaciDialog.getInstanceNacteni();
                            }

                            // doslo od protivnika, ze neakceptuje navrh na load hry
                            if (response.indexOf("LOAD_GAME_DECLINE: ") == 0) {
                                SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.ZAMITACI_DIALOG_NADPIS), PropertiesManager.getInstance().getMessageForKey(Text.LOAD_DIALOG_ZPRAVA_RESULT_DECLINE));
                                NacteniShowPanel.getConnectExceptionText().setText("");
                            }

                            // doslo od protivnika, ze akceptuje navrh na load hry
                            if (response.indexOf("LOAD_GAME_ACCEPT: ") == 0) {
                                NacteniShowPanel.getConnectExceptionText().setText("");
                                try {
                                	Game loadedGame = SaveLoadUtils.loadGame(NacteniShowPanel.getGameName());                                                                

                                	// hra muze byt i jineho typu nez prave loadovana
                                	setCurrentGamePanelFromLoadedGame(loadedGame);
                                
                                	PanelHolderUtils.getCurrentGamePanel().setActiveToGame(true);   
                                	Serializable gameToServer = SaveLoadManager.getInstance().getSaveLoadExecutor().getObjectForSendingToServer(loadedGame);
                                	sendToServer(gameToServer);  
                                	loadedGame.setMeWhite(loadedGame.getWhitePlayerName().equals(getClientName()));
                                	PanelHolderUtils.getCurrentGamePanel().setAndInitGame(loadedGame);
                                	PanelHolderUtils.getCurrentGamePanel().setActiveToGame(true);
                                }
                                catch (SaveLoadException sle) {
                                	sle.printStackTrace();
                                	NacteniShowPanel.getConnectExceptionText().setText(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_LOAD_GAME));
                                }
                            }
                            // od protivnika dosla Game ve String formatu (XML nebo jiny format)
                            if (response.indexOf("LOAD_GAME_HERE: ") == 0) {
                            	Game loadedGame = SaveLoadManager.getInstance().getSaveLoadExecutor().getGameFromServer(response);
                            	setGameAfterGameLoadedFromServer(loadedGame);      
                            }

                            // doslo od protivnika ze akceptuje nas navrh na remizu
                            if (response.indexOf("ACCEPT_REMIZA: ") == 0) {
                                doRemiza();
                                RemizaShowPanel.setConnectExceptionText("");   
                            }

                            // doslo od portivnika ze neakceptuje nas navrh na remizu
                            if (response.indexOf("NOT_ACCEPT_REMIZA: ") == 0) {
                                SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.REMIZA_RESULT_TITLE), PropertiesManager.getInstance().getMessageForKey(Text.REMIZA_RESULT_NOT_ACCEPT_MESSAGE));
                                RemizaShowPanel.setConnectExceptionText("");
                            }

                            // dosle od protivnika navrh na prohru (spis info o tom, ze vzdava hru)
                            if (response.indexOf("PROHRA_NAVRH: ") == 0) {
                            	PanelHolderUtils.getCurrentGamePanel().setActiveToGame(false);
                            	PanelHolderUtils.getCurrentGamePanel().setEndStateToGame(true);
                                SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.VYHRA), PropertiesManager.getInstance().getMessageForKey(Text.VZDAL_VYHRA_MESSAGE));
                            }
                            
                            // *********** SPECIFICKE PRO HRU SACHY *********** //
                            
                            // protivnikuv pesec dosel do konce. Musim cekat...
                            if (response.indexOf("PESEC_DOSEL: ") == 0) {
                            	PanelHolderUtils.getCurrentGamePanel().performSpecialEvent(GamePanel.EVENT_CHESS_PESEC_DOSEL, response);                            	
                            }

                            // protivnikuv pesec dosel do konce. Musim cekat...
                            if (response.indexOf("VYBRANA_FIGURKA: ") == 0) {
                            	PanelHolderUtils.getCurrentGamePanel().performSpecialEvent(GamePanel.EVENT_CHESS_VYBRANA_FIGURKA, response);                            	
                            }   
                            
                            // ******************** KONEC SPECIFICKE PRO HRU SACHY ********************** //

                            //System.out.println(response);
                        }
                    } else { 
                    	// Some other Object than String is received from Server. Only known case is "Game" received.
                    	Game loadedGame = SaveLoadManager.getInstance().getSaveLoadExecutor().getGameFromServer((Serializable)o);
                    	setGameAfterGameLoadedFromServer(loadedGame);      
                    }
                }
                catch (Exception e) {
                	if (e instanceof EOFException) {
                		System.err.println("WARNING: ObjectInputStream read error in ConnectThread. Stream probably closed.");
                	}
                	else {
                		e.printStackTrace();
                	}

                    // spadlo spojeni se serverem
                    //if (e instanceof SocketException) {
                    SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.ERROR_NADPIS), PropertiesManager.getInstance().getMessageForKey(Text.ERROR_SERVER_FALL));
                    NastaveniShowPanel.getConnectLabel().notConnected();
                    PanelHolderUtils.getCurrentGamePanel().setActiveToGame(false);
                    dispose();
                    break;

                    //}
                }
            }
        }
    }

    // na server zasle message, ktera je vetsinou String. Vyjimecne to muze byt i instance objektu Game.
    public void sendToServer(Serializable message) {
        if (ooutputWriter != null) {
            try {
                ooutputWriter.writeObject(message);
                ooutputWriter.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // volano pri vyzvani protivnika se jmenem opName
    public void vyzvat(String opName) {
        opponentName = "";
        gameKey = NastaveniShowPanel.getGamesComboBox().getPrefixForSelectedGame();
        sendToServer("VYZVAT: " + opName + "_" + gameKey);
    }

    // reakce na to, ze serveru doslo potvrzeni me vyzvy
    private void potvrzeni_vyzvy(String opName) {
    	opponentName = opName;
    	NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);
        NastaveniShowPanel.getConnectExceptionText().setText(PropertiesManager.getInstance().getMessageForKey(Text.WAIT_FOR_OPPONENT));
    }

    // stisknuti tlacitka na prijmuti vyzvy
    public void acceptVyzva() {
        sendToServer("ACCEPT: " + opponentName);
        // TADY ZACINA NOVA HRA. Ja jako 'ten co byl vyzvan' hraju za bile.
        setNewGame(clientName, opponentName, gameKey);
    }

    // stisknuti tlacitka na odmitnuti vyzvy
    public void declineVyzva() {
        sendToServer("NOT_ACCEPT: " + opponentName);

        // vynulovani oponentova jmena pri zamitnuti
        opponentName = "";
        NastaveniShowPanel.getListOfUsers().setUserList(response, clientName, opponentName);
    }

    // stisk tlacitka na prijmuti remizy
    public void acceptRemiza() {
        sendToServer("ACCEPT_REMIZA: ");
        doRemiza();
    }
    
    // provede kroky nezbytne k ukonceni hry remizou
    public void doRemiza() {        
       	PanelHolderUtils.getCurrentGamePanel().setActiveToGame(false);
    	PanelHolderUtils.getCurrentGamePanel().setEndStateToGame(true);
        SimpleDialog.getInstance(PropertiesManager.getInstance().getMessageForKey(Text.REMIZA_RESULT_TITLE), PropertiesManager.getInstance().getMessageForKey(Text.REMIZA_RESULT_MESSAGE));                 	
    }

    // stisk tlacitka na odmitnuti remizy
    public void declineRemiza() {
        sendToServer("NOT_ACCEPT_REMIZA: ");
    }

    // stisk tlacitka na akceptovani nacteni hry
    public void acceptLoadGame() {
        sendToServer("LOAD_GAME_ACCEPT: ");
    }

    // stisk tlacitka na akceptovani nacteni hry
    public void declineLoadGame() {
        sendToServer("LOAD_GAME_DECLINE: ");
    }
    
    // nasetovani nove hry
    private void setNewGame(String whitePlayerName, String blackPlayerName, String gameKey) {
    	NastaveniShowPanel.getGamesComboBox().setValueAccordingToKey(gameKey);
    	((PossoftNetChess)(PanelHolderUtils.getApplet())).setGamePanelAccordingToComboBox();
    	PanelHolderUtils.getCurrentGamePanel().setAndInitGame(PanelHolderUtils.getCurrentGamePanel().getGame().setNewGameState(whitePlayerName, blackPlayerName, whitePlayerName.equals(getClientName())));
    	PanelHolderUtils.getCurrentGamePanel().setActiveToGame(true);
    	PanelHolderUtils.getCurrentGamePanel().setEndStateToGame(false);
    	ChatPanel.getChatTextPane().clearDocument();
    }   
    
    private void setCurrentGamePanelFromLoadedGame(Game loadedGame) {
        // hra muze byt i jineho typu nez prave aktivni.
        String gameClassKey = GameInvocationUtils.getKeyOfGame(loadedGame);
        NastaveniShowPanel.getGamesComboBox().setValueAccordingToKey(gameClassKey);
        PanelHolderUtils.setCurrentGamePanelFromPrefix(gameClassKey);    	
    }
    
    /**
     * This is done when new game is obtained from server. Note: It's possible that Game is other type than actually active Game.
     * 
     * @param loadedGame
     */
    private void setGameAfterGameLoadedFromServer(Game loadedGame) {
        setCurrentGamePanelFromLoadedGame(loadedGame);                        
        loadedGame.setMeWhite(loadedGame.getWhitePlayerName().equals(getClientName()));
        PanelHolderUtils.getCurrentGamePanel().setAndInitGame(loadedGame);
        PanelHolderUtils.getCurrentGamePanel().setActiveToGame(true);             	
    }
}